package com.lambkit.db.enjoy;

import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.Const;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.Id;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
import com.lambkit.db.Sql;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;

public class ParaDirective  extends Directive {
    private int index = -1;
    private String paraName = null;
    private static boolean checkParaAssigned = true;
    private int type = 0;
    private static final int TYPE_LIKE = 1;
    private static final int TYPE_LIKE_LEFT = 2;
    private static final int TYPE_LIKE_RIGHT = 3;
    private static final int TYPE_IN = 4;

    public ParaDirective() {
    }

    public static void setCheckParaAssigned(boolean checkParaAssigned) {
        ParaDirective.checkParaAssigned = checkParaAssigned;
    }

    public void setExprList(ExprList exprList) {
        if (exprList.length() == 0) {
            throw new ParseException("The parameter of #para directive can not be blank", this.location);
        } else {
            Expr expr = exprList.getExpr(0);
            if (expr instanceof Const && ((Const)expr).isInt()) {
                this.index = ((Const)expr).getInt();
                if (this.index < 0) {
                    throw new ParseException("The index of para array must greater than -1", this.location);
                }
            }

            if (exprList.length() > 1) {
                expr = exprList.getExpr(1);
                if (expr instanceof Const && ((Const)expr).isStr()) {
                    String typeStr = ((Const)expr).getStr();
                    if (!"like".equalsIgnoreCase(typeStr) && !"%like%".equalsIgnoreCase(typeStr)) {
                        if ("%like".equalsIgnoreCase(typeStr)) {
                            this.type = 2;
                        } else if ("like%".equalsIgnoreCase(typeStr)) {
                            this.type = 3;
                        } else {
                            if (!"in".equalsIgnoreCase(typeStr)) {
                                throw new ParseException("The type of para must be: like, %like, like%, in. Not support : " + typeStr, this.location);
                            }

                            this.type = 4;
                        }
                    } else {
                        this.type = 1;
                    }
                }
            }

            if (checkParaAssigned && exprList.getExpr(0) instanceof Id) {
                Id id = (Id)exprList.getExpr(0);
                this.paraName = id.getId();
            }

            this.exprList = exprList;
        }
    }

    public void exec(Env env, Scope scope, Writer writer) {
        Sql sqlPara = (Sql)scope.get("_SQL_PARA_");
        if (sqlPara == null) {
            throw new TemplateException("#para directive invoked by getSqlPara(...) method only", this.location);
        } else {
            if (this.index == -1) {
                if (checkParaAssigned && this.paraName != null && !scope.exists(this.paraName)) {
                    throw new TemplateException("The parameter \"" + this.paraName + "\" must be assigned", this.location);
                }

                this.handleSqlPara(writer, sqlPara, this.exprList.getExpr(0).eval(scope));
            } else {
                Object[] paras = (Object[])((Object[])scope.get("_PARA_ARRAY_"));
                if (paras == null) {
                    throw new TemplateException("The #para(" + this.index + ") directive must invoked by template(String, Object...) or getSqlPara(String, Object...) method", this.location);
                }

                if (this.index >= paras.length) {
                    throw new TemplateException("The index of #para directive is out of bounds: " + this.index, this.location);
                }

                this.handleSqlPara(writer, sqlPara, paras[this.index]);
            }

        }
    }

    private void handleSqlPara(Writer writer, Sql sqlPara, Object value) {
        if (this.type == 0) {
            this.write(writer, "?");
            sqlPara.addPara(value);
        } else if (this.type == 1) {
            this.write(writer, "?");
            sqlPara.addPara("%" + value + "%");
        } else if (this.type == 2) {
            this.write(writer, "?");
            sqlPara.addPara("%" + value);
        } else if (this.type == 3) {
            this.write(writer, "?");
            sqlPara.addPara(value + "%");
        } else if (this.type == 4) {
            if (value instanceof Collection) {
                this.handleCollection(writer, sqlPara, (Collection)value);
            } else if (value != null && value.getClass().isArray()) {
                this.handleArray(writer, sqlPara, value);
            } else {
                this.write(writer, "(?)");
                sqlPara.addPara(value);
            }
        }

    }

    private void handleCollection(Writer writer, Sql sqlPara, Collection<?> collection) {
        this.write(writer, "(");
        boolean first = true;

        Object element;
        for(Iterator var5 = collection.iterator(); var5.hasNext(); sqlPara.addPara(element)) {
            element = var5.next();
            if (first) {
                first = false;
                this.write(writer, "?");
            } else {
                this.write(writer, ", ?");
            }
        }

        this.write(writer, ")");
    }

    private void handleArray(Writer writer, Sql sqlPara, Object array) {
        this.write(writer, "(");
        int size = Array.getLength(array);

        for(int i = 0; i < size; ++i) {
            if (i == 0) {
                this.write(writer, "?");
            } else {
                this.write(writer, ", ?");
            }

            sqlPara.addPara(Array.get(array, i));
        }

        this.write(writer, ")");
    }
}
